package som;

import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import io.DataSet;

public class ForestFiresNetwork1D extends Network1D {

	/*
	 //returnList.add(Double.parseDouble(dataSet.getValue(row, 0))); //x
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 1))); //y
		//returnList.add(getMonthSin(getNumericMonth(dataSet.getValue(row, 2)))); //month_sin
		//returnList.add(getMonthCos(getNumericMonth(dataSet.getValue(row, 2)))); //month_cos
		//Day unused.
		returnList.add(Double.parseDouble(dataSet.getValue(row, 4))); //ffmc
		returnList.add(Double.parseDouble(dataSet.getValue(row, 5))); //dmc
		returnList.add(Double.parseDouble(dataSet.getValue(row, 6))); //dc
		returnList.add(Double.parseDouble(dataSet.getValue(row, 7))); //isi
		returnList.add(Double.parseDouble(dataSet.getValue(row, 8))); //temp
		returnList.add(Double.parseDouble(dataSet.getValue(row, 9))); //rh
		returnList.add(Double.parseDouble(dataSet.getValue(row, 10))); //wind
		returnList.add(Double.parseDouble(dataSet.getValue(row, 11))); //rain
	 */
	private static final double X_SIGMA = 3.0;
	private static final double Y_SIGMA = 2.0;
	private static final double MONTH_SIN_SIGMA = 0.1;
	private static final double MONTH_COS_SIGMA = 0.1;
	private static final double DAY_SIN_SIGMA = 0.1;
	private static final double DAY_COS_SIGMA = 0.1;
	private static final double FFMC_SIGMA = 5.5;
	private static final double DMC_SIGMA = 100.0;
	private static final double DC_SIGMA = 110.0;
	private static final double ISI_SIGMA = 6.0;
	private static final double TEMP_SIGMA = 10.0;
	private static final double RH_SIGMA = 20.0;
	private static final double WIND_SIGMA = 2.0;
	private static final double RAIN_SIGMA = 1.0; //Almost all zero.
	
	private static final double BUCKET_12_THRESHOLD = 0.5;
	private static final double BUCKET_23_THRESHOLD = 30.0;
	
	public static final double ACCEPTABLE_ERROR = 7.0; //in hectares.
	
	private double[] continuous_classification;
	
	public static DecimalFormat df = new DecimalFormat("000.000");
	
	public ForestFiresNetwork1D(int width, int dimension) {
		super(width, dimension);
		/*
		File f = new File(System.getProperty("java.class.path"));
		File dir = f.getAbsoluteFile().getParentFile();
		String path = dir.toString();
		dataSet = new DataSet(path + "/data/forestfires.csv");
		*/
		
		dataSet = new DataSet("data/forestfires.csv");
		continuous_classification = new double[width];
	}

	@Override
	public void run() {
		
		int num_entries = dataSet.getRowSize();
		for(int repeats=0; repeats<3; repeats++) {
			for(int i=0; i<num_entries; i++) {
				
				//Output attribute. Log is used because the data is very skewed towards zero.
				//double area = Math.log(Double.parseDouble(dataSet.getValue(i, 12) + 1));
				
				train(i + num_entries*repeats, createInputArray(i));
				
				/*
				//Little test...
				if(i + num_entries*repeats > 480) {
					if(i + num_entries*repeats < 500) {
						runContinuousCategorization();
					}
				}
				*/
			}
		}
	}
	
	@Override
	public void runCategorization() {
		
		double[] bucket1 = new double[width]; //Negligible fires.
		double[] bucket2 = new double[width]; //Small fires.
		double[] bucket3 = new double[width]; //Large fires.
		
		for(int p=0; p<width; p++) {
			bucket1[p] = 0;
			bucket2[p] = 0;
			bucket3[p] = 0;
			classification[p] = 0;
			}
		
		int p_winner;
		double u_winner;
		
		for(int input=0; input<dataSet.getRowSize(); input++) {
			p_winner = -1;
			u_winner = Double.NEGATIVE_INFINITY;
			for(int p=0; p<width; p++) {
				Neuron n = inputLayer.get(p);
				double u = n.excite(createInputArray(input));
				if(u > u_winner) {
					u_winner = u;
					p_winner = p;
				}
			}
			
			double fire_area = Double.parseDouble(dataSet.getValue(input, 12));
			if(fire_area < BUCKET_12_THRESHOLD) {
				bucket1[p_winner] += 1;
			}
			else if(fire_area < BUCKET_23_THRESHOLD) {
				bucket2[p_winner] += 1;
			}
			else {
				bucket3[p_winner] += 1;
			}
		}
		
		for(int p=0; p<width; p++) {
			if(bucket1[p] > bucket2[p] && bucket1[p] > bucket3[p]) {
				classification[p] = 1;
			}
			else if(bucket2[p] > bucket1[p] && bucket2[p] > bucket3[p]) {
				classification[p] = 2;
			}
			else if(bucket3[p] > bucket1[p] && bucket3[p] > bucket2[p]) {
				classification[p] = 3;
			}
		}
		
		for(int p=0; p<width; p++) {
			System.out.print("[" + (classification[p] == 0 ? "?" : classification[p]) +"]");
		}
	}
	
	@Override
	public void runContinuousCategorization() {
		
		double[] bucket = new double[width];
		int[] count = new int[width];
		
		for(int p=0; p<width; p++) {
			bucket[p] = 0;
			count[p] = 0;
			continuous_classification[p] = 0;
		}
		
		int p_winner;
		double u_winner;
		
		for(int input=0; input<dataSet.getRowSize(); input++) {
			p_winner = -1;
			u_winner = Double.NEGATIVE_INFINITY;
			for(int p=0; p<width; p++) {
				Neuron n = inputLayer.get(p);
				double u = n.excite(createInputArray(input));
				if(u > u_winner) {
					u_winner = u;
					p_winner = p;
				}
			}
			double fire_area = Double.parseDouble(dataSet.getValue(input, 12));
			count[p_winner]++;
			bucket[p_winner] += fire_area;
		}
		
		for(int p=0; p<width; p++) {
			continuous_classification[p] = bucket[p]/count[p];
		}
		
		for(int p=0; p<width; p++) {
			System.out.println(p + "\t" + (count[p] == 0 ? "?" : df.format(continuous_classification[p])));
		}
	}
	
	@Override
	public void runUMatrix() {
		
		double[] umatrix = new double[width];
		double sum = 0;
		double u_value;
		
		//left
		for(int s=0; s<dimension; s++) {
			sum += Math.pow(inputLayer.get(0).getSynapse().get(s) - inputLayer.get(1).getSynapse().get(s), 2);
		}
		
		u_value = sum;
		umatrix[0] = u_value;
		
		//inside
		for(int i=1; i<width-1; i++) {
			sum = 0;
			for(int s=0; s<dimension; s++) {
				sum += Math.pow(inputLayer.get(i).getSynapse().get(s) - inputLayer.get(i-1).getSynapse().get(s), 2);
			}
			u_value = sum;
			for(int s=0; s<dimension; s++) {
				sum += Math.pow(inputLayer.get(i).getSynapse().get(s) - inputLayer.get(i+1).getSynapse().get(s), 2);
			}
			u_value += sum;
			u_value /= 2;
			umatrix[i] = u_value;
		}
		
		//right
		sum = 0;
		for(int s=0; s<dimension; s++) {
			sum += Math.pow(inputLayer.get(width-1).getSynapse().get(s) - inputLayer.get(width-2).getSynapse().get(s), 2);
		}
		
		u_value = sum;
		umatrix[width-1] = u_value;
		
		for(int p=0; p<width; p++) {
			System.out.println(p + " " + df.format(umatrix[p]));
		}
	}
	
	public boolean assertCorrectness(int row) {
		Double[] input = createInputArray(row);
		
		if(input.length != dimension)
			throw new IllegalArgumentException("position array must have the same size as dimension");
		
		int p_winner = -1;
		int q_winner = -1;
		double u_winner = VigilanceNeuron.VIGILANCE_RADIUS;
		
		for(int p=0; p<width; p++) {
			Neuron n = inputLayer.get(p);
			double u = n.excite(input);
			if(u > u_winner) {
				u_winner = u;
				p_winner = p;
			}
		}
		
		double fire_area = Double.parseDouble(dataSet.getValue(row, 12));
		
		int actual_class;
		
		if(fire_area < BUCKET_12_THRESHOLD) {
			actual_class = 1;
		}
		else if(fire_area < BUCKET_23_THRESHOLD) {
			actual_class = 2;
		}
		else {
			actual_class = 3;
		}
		
		if(p_winner == -1)
			return false;
		int result_class = classification[p_winner];
		
		if(actual_class == result_class)
			return true;
		else
			return false;
	}
	
	@Override
	public double assertError(int row) {
		Double[] input = createInputArray(row);
		
		if(input.length != dimension)
			throw new IllegalArgumentException("position array must have the same size as dimension");
		
		int p_winner = -1;
		double u_winner = VigilanceNeuron.VIGILANCE_RADIUS;
		
		for(int p=0; p<width; p++) {
			Neuron n = inputLayer.get(p);
			double u = n.excite(input);
			if(u > u_winner) {
				u_winner = u;
				p_winner = p;
			}
		}
		
		if(p_winner == -1)
			return Double.MAX_VALUE;
		
		double fire_area = Double.parseDouble(dataSet.getValue(row, 12));
		
		double error = Math.abs(continuous_classification[p_winner] - fire_area);
		
		return error;
	}

	public int getNumericMonth(String month) {
		int numeric;
		if(month.equals("jan")) {
			numeric = 0;
		}
		else if(month.equals("feb")) {
			numeric = 1;
		}
		else if(month.equals("mar")) {
			numeric = 2;
		}
		else if(month.equals("apr")) {
			numeric = 3;
		}
		else if(month.equals("may")) {
			numeric = 4;
		}
		else if(month.equals("jun")) {
			numeric = 5;
		}
		else if(month.equals("jul")) {
			numeric = 6;
		}
		else if(month.equals("aug")) {
			numeric = 7;
		}
		else if(month.equals("sep")) {
			numeric = 8;
		}
		else if(month.equals("oct")) {
			numeric = 9;
		}
		else if(month.equals("nov")) {
			numeric = 10;
		}
		else if(month.equals("dec")) {
			numeric = 11;
		}
		else
			numeric = -1;
		
		return numeric;
	}
	
	public int getNumericDay(String day) {
		int numeric;
		if(day.equals("mon")) {
			numeric = 0;
		}
		else if(day.equals("tue")) {
			numeric = 1;
		}
		else if(day.equals("wed")) {
			numeric = 2;
		}
		else if(day.equals("thu")) {
			numeric = 3;
		}
		else if(day.equals("fri")) {
			numeric = 4;
		}
		else if(day.equals("sat")) {
			numeric = 5;
		}
		else if(day.equals("sun")) {
			numeric = 6;
		}
		else
			numeric = -1;
		
		return numeric;
	}
	
	public double getMonthSin(int month) {
		return Math.sin(2.0*Math.PI*month/12.0);
	}
	
	public double getMonthCos(int month) {
		return Math.cos(2.0*Math.PI*month/12.0);
	}
	
	public double getDaySin(int day) {
		return Math.sin(2.0*Math.PI*day/7.0);
	}
	
	public double getDayCos(int day) {
		return Math.cos(2.0*Math.PI*day/7.0);
	}
	
	public Double[] createInputArray(int row) {
		
		List<Double> returnList = new ArrayList<Double>();
		
		Double[] returnArray;
		
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 0))/X_SIGMA); //x
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 1))/Y_SIGMA); //y
		//returnList.add(getMonthSin(getNumericMonth(dataSet.getValue(row, 2)))/MONTH_SIN_SIGMA); //month_sin
		//returnList.add(getMonthCos(getNumericMonth(dataSet.getValue(row, 2)))/MONTH_COS_SIGMA); //month_cos
		//returnList.add(getDaySin(getNumericDay(dataSet.getValue(row, 3)))/DAY_SIN_SIGMA); //day_sin
		//returnList.add(getDayCos(getNumericDay(dataSet.getValue(row, 3)))/DAY_COS_SIGMA); //day_cos
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 4))/FFMC_SIGMA); //ffmc
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 5))/DMC_SIGMA); //dmc
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 6))/DC_SIGMA); //dc
		//returnList.add(Double.parseDouble(dataSet.getValue(row, 7))/ISI_SIGMA); //isi
		returnList.add(Double.parseDouble(dataSet.getValue(row, 8))/TEMP_SIGMA); //temp
		returnList.add(Double.parseDouble(dataSet.getValue(row, 9))/RH_SIGMA); //rh
		returnList.add(Double.parseDouble(dataSet.getValue(row, 10))/WIND_SIGMA); //wind
		returnList.add(Double.parseDouble(dataSet.getValue(row, 11))/RAIN_SIGMA); //rain
		
		returnArray = new Double[returnList.size()];
		return returnList.toArray(returnArray);
	}
}
